package org.jfab.core.factory;

import java.beans.PropertyChangeListener;
import java.util.Collections;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.Validate;
import org.jfab.core.collection.CGAnnotationList;
import org.jfab.core.collection.CGMethodDeclarationList;
import org.jfab.core.collection.CGParameterList;
import org.jfab.core.collection.CGPropertyList;
import org.jfab.core.collection.CGTypeList;
import org.jfab.core.collection.CommentList;
import org.jfab.core.collection.ModifierList;
import org.jfab.core.model.CGMethod;
import org.jfab.core.model.CGMethodDeclaration;
import org.jfab.core.model.CGParameter;
import org.jfab.core.model.CGProperty;
import org.jfab.core.model.CGType;
import org.jfab.core.model.Modifier;

/**
 * Provides a method declaration factory.
 */
public class DefaultCGMethodDeclarationFactory implements
        CGMethodDeclarationFactory
{
    /** Parameter factory. */
    private final CGParameterFactory _parameterFactory;

    /** String factory. */
    private final StringFactory _stringFactory;

    /** Type factory. */
    private final CGTypeFactory _typeFactory;

    /**
     * Construct this object with the given parameters.
     * 
     * @param typeFactory Type factory.
     * @param stringFactory String factory.
     * @param parameterFactory Parameter factory.
     */
    public DefaultCGMethodDeclarationFactory(final CGTypeFactory typeFactory,
            final StringFactory stringFactory,
            final CGParameterFactory parameterFactory)
    {
        _typeFactory = typeFactory;
        _stringFactory = stringFactory;
        _parameterFactory = parameterFactory;
    }

    @Override
    public final CGMethodDeclaration createAddPropertyChangeListenerMethod(
            final CGAnnotationList annotations, final ModifierList modifiers)
    {
        final CommentList commentLines = new CommentList();
        commentLines
                .add("Add a <code>PropertyChangeListener</code> to the listener list. The");
        commentLines
                .add("listener is registered for all properties. This method will only add the");
        commentLines.add("listener if it is not already registered.");
        commentLines.add("");
        commentLines
                .add("@param  listener  The <code>PropertyChangeListener</code> to be added.");

        final CGTypeList referencedTypes = null;
        final CGType returnType = null;
        final String methodName = "addPropertyChangeListener";
        final CGParameter parameter = getParameterFactory().createParameter(
                getTypeFactory().createType(PropertyChangeListener.class),
                "listener");
        final CGParameterList parameters = new CGParameterList(
                Collections.singletonList(parameter));
        final CGTypeList throwsTypes = null;

        return createMethod(referencedTypes, commentLines, annotations,
                modifiers, returnType, methodName, parameters, throwsTypes);
    }

    @Override
    public final CGMethodDeclaration createAddPropertyChangeListenerNamedMethod(
            final CGAnnotationList annotations, final ModifierList modifiers)
    {
        final CommentList commentLines = new CommentList();
        commentLines
                .add("Add a <code>PropertyChangeListener</code> for a specific property. The");
        commentLines.add("listener will be invoked only when a call on <code>");
        commentLines
                .add("firePropertyChange</code> names that specific property.");
        commentLines.add("");
        commentLines
                .add("@param  propertyName  The name of the property to listen on.");
        commentLines
                .add("@param  listener      The <code>PropertyChangeListener</code> to be added.");

        final CGTypeList referencedTypes = null;
        final CGType returnType = null;
        final String methodName = "addPropertyChangeListener";
        final CGParameter parameter0 = getParameterFactory().createParameter(
                getTypeFactory().createType(String.class), "propertyName");
        final CGParameter parameter1 = getParameterFactory().createParameter(
                getTypeFactory().createType(PropertyChangeListener.class),
                "listener");
        final CGParameterList parameters = new CGParameterList();
        parameters.add(parameter0);
        parameters.add(parameter1);

        final CGTypeList throwsTypes = null;

        return createMethod(referencedTypes, commentLines, annotations,
                modifiers, returnType, methodName, parameters, throwsTypes);
    }

    @Override
    public final CGMethodDeclaration createMethod(
            final CGTypeList referencedTypes, final CommentList commentLines,
            final CGAnnotationList annotations, final ModifierList modifiers,
            final CGType returnType, final String methodName,
            final CGParameterList parameters, final CGTypeList throwsTypes)
    {
        Validate.notEmpty(methodName, "methodName is empty");

        final CGMethod answer = new CGMethod();

        if (referencedTypes != null)
        {
            answer.getReferencedTypes().addAll(referencedTypes);
        }

        if (commentLines != null)
        {
            answer.getCommentLines().addAll(commentLines);
        }

        if (annotations != null)
        {
            answer.getAnnotations().addAll(annotations);
        }

        if (modifiers != null)
        {
            answer.getModifiers().addAll(modifiers);
        }

        if (returnType != null)
        {
            answer.setReturnType(returnType);
        }

        answer.setName(methodName);

        if (parameters != null)
        {
            answer.getParameters().addAll(parameters);
        }

        if (throwsTypes != null)
        {
            answer.getThrowsTypes().addAll(throwsTypes);
        }

        return answer;
    }

    @Override
    public CGMethodDeclarationList createPropertyAccessorMethods(
            final CGAnnotationList annotations, final ModifierList modifiers,
            final CGPropertyList properties)
    {
        final CGMethodDeclarationList answer = new CGMethodDeclarationList();

        for (final CGProperty property : properties)
        {
            if (isGetter(property))
            {
                final CGType propertyType = property.getType();
                final String propertyName = property.getName();
                final CGMethodDeclaration method = createAccessorMethod(
                        annotations, modifiers, propertyType, propertyName);
                answer.add(method);
            }
        }

        return answer;
    }

    @Override
    public CGMethodDeclarationList createPropertyMutatorMethods(
            final ModifierList modifiers, final CGPropertyList properties)
    {
        final CGMethodDeclarationList answer = new CGMethodDeclarationList();

        for (final CGProperty property : properties)
        {
            if (isSetter(property))
            {
                if ((property.getModifiers() == null)
                        || ((property.getModifiers() != null) && !property
                                .getModifiers().contains(
                                        Modifier.FINAL.getName())))
                {
                    final CGType propertyType = property.getType();
                    final String propertyName = property.getName();
                    final String paramComment = getStringFactory()
                            .createCommentFromPropertyName(propertyName);
                    final CGMethodDeclaration method = createMutatorMethod(
                            modifiers, propertyType, propertyName, paramComment);
                    answer.add(method);
                }
            }
        }

        return answer;
    }

    @Override
    public CGMethodDeclarationList createPropertyMutatorPropertyChangeMethods(
            final CGAnnotationList annotations, final ModifierList modifiers,
            final CGPropertyList properties)
    {
        final CGMethodDeclarationList answer = new CGMethodDeclarationList();

        if (CollectionUtils.isNotEmpty(properties))
        {
            for (final CGProperty property : properties)
            {
                if (isSetter(property))
                {
                    if ((property.getModifiers() == null)
                            || ((property.getModifiers() != null) && !property
                                    .getModifiers().contains(
                                            Modifier.FINAL.getName())))
                    {
                        final CGType propertyType = property.getType();
                        final String propertyName = property.getName();
                        final String paramComment = propertyName;
                        final CGMethodDeclaration newMethod = createMutatorPropertyChangeMethod(
                                annotations, modifiers, propertyType,
                                propertyName, paramComment);
                        answer.add(newMethod);
                    }
                }
            }
        }

        return answer;
    }

    @Override
    public CGMethodDeclaration createRemovePropertyChangeListenerMethod(
            final CGAnnotationList annotations, final ModifierList modifiers)
    {
        final CGTypeList referencedTypes = null;
        final CommentList commentLines = new CommentList();
        commentLines
                .add("Remove a <code>PropertyChangeListener</code> from the listener list. This");
        commentLines
                .add("removes a <code>PropertyChangeListener</code> that was registered for all");
        commentLines.add("properties.");
        commentLines.add("");
        commentLines
                .add("@param  listener  The <code>PropertyChangeListener</code> to be removed.");

        final CGType returnType = null;
        final String methodName = "removePropertyChangeListener";
        final CGParameter parameter0 = getParameterFactory().createParameter(
                getTypeFactory().createType(PropertyChangeListener.class),
                "listener");
        final CGParameterList parameters = new CGParameterList();
        parameters.add(parameter0);

        final CGTypeList throwsTypes = null;

        return createMethod(referencedTypes, commentLines, annotations,
                modifiers, returnType, methodName, parameters, throwsTypes);
    }

    @Override
    public CGMethodDeclaration createRemovePropertyChangeListenerNamedMethod(
            final CGAnnotationList annotations, final ModifierList modifiers)
    {
        final CGTypeList referencedTypes = null;
        final CommentList commentLines = new CommentList();
        commentLines
                .add("Remove a <code>PropertyChangeListener</code> for a specific property.");
        commentLines.add("");
        commentLines
                .add("@param  propertyName  The name of the property to listen on.");
        commentLines
                .add("@param  listener      The <code>PropertyChangeListener</code> to be removed.");

        final CGType returnType = null;
        final String methodName = "removePropertyChangeListener";
        final CGParameter parameter0 = getParameterFactory().createParameter(
                getTypeFactory().createType(String.class), "propertyName");
        final CGParameter parameter1 = getParameterFactory().createParameter(
                getTypeFactory().createType(PropertyChangeListener.class),
                "listener");
        final CGParameterList parameters = new CGParameterList();
        parameters.add(parameter0);
        parameters.add(parameter1);

        final CGTypeList throwsTypes = null;

        return createMethod(referencedTypes, commentLines, annotations,
                modifiers, returnType, methodName, parameters, throwsTypes);
    }

    /**
     * @param annotations Annotations.
     * @param modifiers Modifiers.
     * @param propertyType Property type.
     * @param propertyName Property name.
     * 
     * @return an accessor method.
     */
    private CGMethodDeclaration createAccessorMethod(
            final CGAnnotationList annotations, final ModifierList modifiers,
            final CGType propertyType, final String propertyName)
    {
        final CGTypeList referencedTypes = null;
        final CommentList commentLines = new CommentList(
                Collections.singletonList("@return " + propertyName));
        final CGType returnType = propertyType;
        final String methodName = getStringFactory().createAccessorNameFor(
                propertyType, propertyName);
        final CGParameterList parameters = null;
        final CGTypeList throwsTypes = null;

        return createMethod(referencedTypes, commentLines, annotations,
                modifiers, returnType, methodName, parameters, throwsTypes);
    }

    /**
     * @param modifiers Modifiers.
     * @param propertyType Property type.
     * @param propertyName Property name.
     * @param paramComment Parameter comment.
     * 
     * @return a mutator method.
     */
    private CGMethodDeclaration createMutatorMethod(
            final ModifierList modifiers, final CGType propertyType,
            final String propertyName, final String paramComment)
    {
        final CGTypeList referencedTypes = null;
        final CommentList commentLines = new CommentList(
                Collections.singletonList("@param " + propertyName + "  "
                        + paramComment));
        final CGAnnotationList annotations = null;
        final CGType returnType = null;
        final String methodName = getStringFactory().createMutatorNameFor(
                propertyName);
        final CGParameter parameter0 = getParameterFactory().createParameter(
                propertyType, propertyName);
        final CGParameterList parameters = new CGParameterList();
        parameters.add(parameter0);

        final CGTypeList throwsTypes = null;

        return createMethod(referencedTypes, commentLines, annotations,
                modifiers, returnType, methodName, parameters, throwsTypes);
    }

    /**
     * @param annotations Annotations.
     * @param modifiers Modifiers.
     * @param propertyType Property type.
     * @param propertyName Property name.
     * @param paramComment Parameter comment.
     * 
     * @return a mutator property change method.
     */
    private CGMethodDeclaration createMutatorPropertyChangeMethod(
            final CGAnnotationList annotations, final ModifierList modifiers,
            final CGType propertyType, final String propertyName,
            final String paramComment)
    {
        final String parameterName = propertyName + "In";

        final CGTypeList referencedTypes = null;
        final CommentList commentLines = new CommentList(
                Collections.singletonList("@param " + parameterName + "  "
                        + paramComment));
        final CGType returnType = null;
        final String methodName = getStringFactory().createMutatorNameFor(
                propertyName);
        final CGParameter parameter0 = getParameterFactory().createParameter(
                propertyType, parameterName);
        final CGParameterList parameters = new CGParameterList();
        parameters.add(parameter0);

        final CGTypeList throwsTypes = null;

        return createMethod(referencedTypes, commentLines, annotations,
                modifiers, returnType, methodName, parameters, throwsTypes);
    }

    /**
     * @return the parameterFactory
     */
    private CGParameterFactory getParameterFactory()
    {
        return _parameterFactory;
    }

    /**
     * @return the stringFactory
     */
    private StringFactory getStringFactory()
    {
        return _stringFactory;
    }

    /**
     * @return the typeFactory
     */
    private CGTypeFactory getTypeFactory()
    {
        return _typeFactory;
    }

    /**
     * @param cgProperty Code generator property.
     * 
     * @return true if the given parameter has a getter method.
     */
    private boolean isGetter(final CGProperty cgProperty)
    {
        return (cgProperty.isGetter() != null) && cgProperty.isGetter();
    }

    /**
     * @param cgProperty Code generator property.
     * 
     * @return true if the given parameter has a setter method.
     */
    private boolean isSetter(final CGProperty cgProperty)
    {
        return (cgProperty.isSetter() != null) && cgProperty.isSetter();
    }
}
